from fastapi import FastAPI, Depends, HTTPException, Request, Form, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials, HTTPBearer, HTTPAuthorizationCredentials
from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy.orm import Session
import os
import secrets
import json
import logging
import time
from typing import List, Optional
from datetime import date, datetime

import crud, schemas, database
from database import get_db, create_tables
from llm_api import generate_questions_from_prompt, LLMAPI
from cache import CacheManager
from config import config
from security import (
    SecurityConfig, SecurityHeaders, InputSanitizer, 
    PasswordValidator, login_tracker, security_audit_log
)

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

# 安全配置
ADMIN_TOKEN = config.ADMIN_TOKEN
RATE_LIMIT_REQUESTS = config.RATE_LIMIT_REQUESTS
rate_limit_storage = {}  # 简单的内存存储

# 初始化安全组件
security_config = config.get_security_config()
security_headers = SecurityHeaders()
input_sanitizer = InputSanitizer()
password_validator = PasswordValidator()

# 创建FastAPI应用
app = FastAPI(
    title="高中信息技术选择题练习平台",
    description="一个用于高中信息技术课的在线选择题练习和管理平台",
    version="1.0.0"
)

# 添加CORS中间件
app.add_middleware(
    CORSMiddleware,
    allow_origins=config.ALLOWED_ORIGINS,  # 通过环境变量配置
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 添加安全头部中间件
@app.middleware("http")
async def add_security_headers(request: Request, call_next):
    response = await call_next(request)
    headers = security_headers.get_security_headers()
    for key, value in headers.items():
        response.headers[key] = value
    return response

# HTTP Basic Auth 和 Bearer Token Auth
security = HTTPBasic()
bearer_security = HTTPBearer(auto_error=False)

def get_client_ip(request: Request) -> str:
    """获取客户端IP地址"""
    forwarded = request.headers.get("X-Forwarded-For")
    if forwarded:
        return forwarded.split(",")[0].strip()
    return request.client.host

def check_rate_limit(client_ip: str) -> bool:
    """检查速率限制"""
    current_time = time.time()
    minute_key = int(current_time // 60)
    
    if client_ip not in rate_limit_storage:
        rate_limit_storage[client_ip] = {}
    
    client_data = rate_limit_storage[client_ip]
    
    # 清理过期数据
    expired_keys = [k for k in client_data.keys() if k < minute_key - 1]
    for key in expired_keys:
        del client_data[key]
    
    # 检查当前分钟的请求数
    current_requests = client_data.get(minute_key, 0)
    if current_requests >= RATE_LIMIT_REQUESTS:
        return False
    
    client_data[minute_key] = current_requests + 1
    return True

# 管理员认证
def authenticate_admin(request: Request, credentials: HTTPBasicCredentials = Depends(security)):
    client_ip = get_client_ip(request)
    
    # 检查登录尝试限制
    if login_tracker.is_locked(client_ip):
        security_audit_log("admin_login_blocked", client_ip, "IP被锁定")
        raise HTTPException(
            status_code=429,
            detail="登录尝试过多，请稍后再试",
            headers={"WWW-Authenticate": "Basic"},
        )
    
    # 输入验证和清理
    username = input_sanitizer.sanitize_string(credentials.username)
    password = credentials.password
    
    correct_username = secrets.compare_digest(username, config.ADMIN_USERNAME)
    correct_password = secrets.compare_digest(password, config.ADMIN_PASSWORD)
    
    if not (correct_username and correct_password):
        login_tracker.record_attempt(client_ip, False)
        security_audit_log("admin_login_failed", client_ip, f"用户名: {username}")
        raise HTTPException(
            status_code=401,
            detail="认证失败",
            headers={"WWW-Authenticate": "Basic"},
        )
    
    login_tracker.record_attempt(client_ip, True)
    security_audit_log("admin_login_success", client_ip, f"用户名: {username}")
    return username

def verify_admin_token(credentials: HTTPAuthorizationCredentials = Depends(bearer_security)) -> bool:
    """验证管理员token"""
    if not credentials:
        return False
    return secrets.compare_digest(credentials.credentials, ADMIN_TOKEN)

# 创建管理员认证依赖
def get_admin_user(request: Request, credentials: HTTPBasicCredentials = Depends(security)):
    """获取管理员用户"""
    return authenticate_admin(request, credentials)

def require_admin(credentials: HTTPAuthorizationCredentials = Depends(bearer_security)):
    """需要管理员权限的依赖"""
    if not verify_admin_token(credentials):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="需要管理员权限",
            headers={"WWW-Authenticate": "Bearer"},
        )

# 启动时创建数据库表
@app.on_event("startup")
async def startup_event():
    create_tables()
    
    # 检查是否需要导入初始题目数据
    db = next(get_db())
    try:
        # 检查是否已有题目数据
        from sqlalchemy import text
        existing_questions = db.execute(text("SELECT COUNT(*) FROM questions")).fetchone()[0]
        if existing_questions == 0:
            logger.info("数据库中无题目数据，开始导入示例题目...")
            
            # 导入示例题目
            sample_questions_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "sample_questions.json")
            # 在Docker容器中，文件位于/app/sample_questions.json
            if not os.path.exists(sample_questions_path):
                sample_questions_path = "/app/sample_questions.json"
            if os.path.exists(sample_questions_path):
                import json
                with open(sample_questions_path, 'r', encoding='utf-8') as f:
                    questions_data = json.load(f)
                
                imported_count = 0
                for question_data in questions_data:
                    try:
                        # 创建题目对象
                        question_create = schemas.QuestionCreate(
                            questionContent=question_data['question'],
                            optionA=question_data['options']['A'],
                            optionB=question_data['options']['B'],
                            optionC=question_data['options']['C'],
                            optionD=question_data['options']['D'],
                            answer=question_data['answer'],
                            knowledgePoint=question_data['knowledge_point']
                        )
                        
                        # 导入题目
                        crud.create_question(db, question_create)
                        imported_count += 1
                    except Exception as e:
                        logger.error(f"导入题目失败: {e}")
                        continue
                
                logger.info(f"成功导入 {imported_count} 道题目")
            else:
                logger.warning(f"示例题目文件不存在: {sample_questions_path}")
        else:
            logger.info(f"数据库中已有 {existing_questions} 道题目，跳过导入")
    except Exception as e:
        logger.error(f"检查或导入题目数据时出错: {e}")
    finally:
        db.close()

@app.get("/health")
async def health_check():
    """健康检查端点"""
    try:
        # 检查数据库连接
        from sqlalchemy import text
        db = next(get_db())
        db.execute(text("SELECT 1"))
        db.close()
        
        return {
            "status": "healthy",
            "timestamp": datetime.now().isoformat(),
            "version": "1.0.0",
            "database": "connected"
        }
    except Exception as e:
        logger.error(f"健康检查失败: {e}")
        raise HTTPException(status_code=503, detail="Service unavailable")

# 静态文件服务（用于前端）
# import os
# frontend_dist_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "frontend", "dist")
# if os.path.exists(frontend_dist_path):
#     app.mount("/static", StaticFiles(directory=frontend_dist_path), name="static")
#     assets_path = os.path.join(frontend_dist_path, "assets")
#     if os.path.exists(assets_path):
#         app.mount("/assets", StaticFiles(directory=assets_path), name="assets")

# 学生API接口
@app.get("/api/questions", response_model=List[schemas.Question])
def get_questions(db: Session = Depends(get_db)):
    """获取激活题库中的题目"""
    questions = crud.get_questions_from_active_bank(db)
    return questions

@app.post("/api/submit")
async def submit_answers(
    submission: schemas.SubmissionCreate,
    request: Request,
    db: Session = Depends(get_db)
):
    try:
        # 获取客户端IP并检查速率限制
        client_ip = get_client_ip(request)
        
        if not check_rate_limit(client_ip):
            raise HTTPException(
                status_code=status.HTTP_429_TOO_MANY_REQUESTS,
                detail="请求过于频繁，请稍后再试"
            )
        
        # 验证输入数据
        if not submission.student_name or not submission.student_name.strip():
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="学生姓名不能为空"
            )

        if not submission.answers:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="答案不能为空"
            )

        # 检查同一IP是否被锁定到特定姓名
        student_name = submission.student_name.strip()
        is_locked, locked_name, remaining_minutes = crud.check_name_lock_by_ip(db, client_ip, student_name, 30)

        if is_locked:
            raise HTTPException(
                status_code=status.HTTP_429_TOO_MANY_REQUESTS,
                detail=f"当前姓名已锁定为「{locked_name}」，请等待 {remaining_minutes} 分钟后才能使用其他姓名，或直接使用「{locked_name}」"
            )
        
        logger.info(f"学生 {submission.student_name} 提交答案，IP: {client_ip}")
        
        # 获取当前班级配置
        class_config = crud.get_class_config(db)
        
        # 使用配置的班级ID，覆盖前端传来的class_id
        submission.class_id = class_config.current_class_id
        
        # 计算分数和详细结果
        score, full_results = crud.calculate_score(db, submission.answers)

        # 计算题目统计信息
        total_questions = len(full_results)
        correct_answers = sum(1 for result in full_results if result.isCorrect)
        accuracy_rate = round((correct_answers / total_questions * 100)) if total_questions > 0 else 0

        # 创建提交记录，传递正确的统计信息
        db_submission = crud.create_submission(
            db, submission, score, client_ip,
            total_questions=total_questions,
            correct_answers=correct_answers
        )

        # 更新提交记录的正确率
        db_submission.accuracy_rate = accuracy_rate
        db.commit()

        # 批量创建提交详情
        crud.batch_create_submission_details(db, db_submission.id, submission.answers)
        
        # 清除相关缓存
        CacheManager.clear_submissions_cache()
        CacheManager.clear_analysis_cache()
        
        logger.info(f"学生 {submission.student_name} 提交成功，得分: {score}")
        
        return {
            "message": "提交成功",
            "submission_id": db_submission.id,
            "score": score,
            "class_id": db_submission.class_id,
            "full_results": [result.dict() for result in full_results]
        }
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"提交答案失败: {str(e)}")
        raise HTTPException(status_code=500, detail="提交失败，请稍后重试")

@app.get("/api/scores/{class_id}", response_model=List[schemas.ScoreRecord])
def get_class_scores(class_id: int, db: Session = Depends(get_db)):
    """获取指定班级当天的成绩"""
    try:
        # 输入验证
        if class_id <= 0:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="班级ID必须为正整数"
            )
        
        logger.info(f"获取班级成绩: class_id={class_id}")
        
        submissions = crud.get_submissions_by_class_and_date(db, class_id)
        
        scores = []
        for submission in submissions:
            score_record = schemas.ScoreRecord(
                submission_id=submission.id,
                student_name=submission.student_name,
                score=submission.score,
                submission_time=submission.submission_time.strftime("%H:%M:%S"),
                client_ip=submission.client_ip
            )
            scores.append(score_record)
        
        logger.info(f"成功获取班级成绩，共 {len(scores)} 条记录")
        return scores
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"获取班级成绩失败: {str(e)}")
        raise HTTPException(status_code=500, detail="获取班级成绩失败")

@app.get("/api/class-analysis/{class_id}", response_model=schemas.ClassAnalysis)
def get_class_analysis(class_id: int, date: str = None, db: Session = Depends(get_db)):
    """获取班级学情分析"""
    try:
        # 输入验证
        if class_id <= 0:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="班级ID必须为正整数"
            )
        
        if date and not date.strip():
            date = None
        
        logger.info(f"获取班级学情分析: class_id={class_id}, date={date}")
        
        analysis = crud.get_class_analysis(db, class_id, date)
        return analysis
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"获取学情分析失败: {str(e)}")
        raise HTTPException(status_code=500, detail="获取学情分析失败")

@app.get("/api/student-trajectory/{class_id}/{student_name}")
def get_student_learning_trajectory(class_id: int, student_name: str, db: Session = Depends(get_db)):
    """获取学生学习轨迹"""
    try:
        # 输入验证
        if class_id <= 0:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="班级ID必须为正整数"
            )
        
        if not student_name or not student_name.strip():
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="学生姓名不能为空"
            )
        
        student_name = student_name.strip()
        logger.info(f"获取学生学习轨迹: class_id={class_id}, student_name={student_name}")
        
        trajectory = crud.get_student_learning_trajectory(db, class_id, student_name)
        return trajectory
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"获取学生学习轨迹失败: {str(e)}")
        raise HTTPException(status_code=500, detail="获取学生学习轨迹失败")

@app.get("/api/error-analysis/{class_id}")
def get_error_analysis(class_id: int, date: str = None, db: Session = Depends(get_db)):
    """获取错题深度分析"""
    try:
        # 输入验证
        if class_id <= 0:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="班级ID必须为正整数"
            )
        
        if date and not date.strip():
            date = None
        
        logger.info(f"获取错题深度分析: class_id={class_id}, date={date}")
        
        analysis = crud.get_error_analysis(db, class_id, date)
        return analysis
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"获取错题分析失败: {str(e)}")
        raise HTTPException(status_code=500, detail="获取错题分析失败")

@app.get("/api/teaching-suggestions/{class_id}")
def get_teaching_suggestions(class_id: int, date: str = None, db: Session = Depends(get_db)):
    """获取教学建议"""
    try:
        # 输入验证
        if class_id <= 0:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="班级ID必须为正整数"
            )
        
        if date and not date.strip():
            date = None
        
        logger.info(f"获取教学建议: class_id={class_id}, date={date}")
        
        suggestions = crud.get_teaching_suggestions(db, class_id, date)
        return suggestions
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"获取教学建议失败: {str(e)}")
        raise HTTPException(status_code=500, detail="获取教学建议失败")

# 学生做题记录查看API
@app.get("/api/student-records")
def get_student_submission_records(
    student_name: str = None,
    class_id: int = None,
    start_date: str = None,
    end_date: str = None,
    skip: int = 0,
    limit: int = 100,
    db: Session = Depends(get_db)
):
    """获取学生做题记录列表"""
    try:
        # 输入验证
        if class_id is not None and class_id <= 0:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="班级ID必须为正整数"
            )
        
        if skip < 0:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="跳过数量不能为负数"
            )
        
        if limit <= 0 or limit > 1000:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="限制数量必须在1-1000之间"
            )
        
        logger.info(f"获取学生做题记录: student_name={student_name}, class_id={class_id}, start_date={start_date}, end_date={end_date}")
        
        records = crud.get_student_submission_records(
            db, student_name, class_id, start_date, end_date, skip, limit
        )
        return {"records": records, "total": len(records)}
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"获取学生做题记录失败: {str(e)}")
        raise HTTPException(status_code=500, detail="获取学生做题记录失败")

@app.get("/api/student-records/{submission_id}")
def get_student_submission_detail(submission_id: int, db: Session = Depends(get_db)):
    """获取学生单次做题的详细记录"""
    try:
        # 输入验证
        if submission_id <= 0:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="提交记录ID必须为正整数"
            )
        
        logger.info(f"获取学生做题详细记录: submission_id={submission_id}")
        
        detail = crud.get_student_submission_detail(db, submission_id)
        if not detail:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="未找到该提交记录"
            )
        
        return detail
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"获取学生做题详细记录失败: {str(e)}")
        raise HTTPException(status_code=500, detail="获取学生做题详细记录失败")

@app.get("/api/student-statistics/{student_name}")
def get_student_statistics(student_name: str, class_id: int = None, db: Session = Depends(get_db)):
    """获取学生做题统计信息"""
    try:
        # 输入验证
        if not student_name or not student_name.strip():
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="学生姓名不能为空"
            )
        
        if class_id is not None and class_id <= 0:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="班级ID必须为正整数"
            )
        
        logger.info(f"获取学生统计信息: student_name={student_name}, class_id={class_id}")
        
        statistics = crud.get_student_statistics(db, student_name.strip(), class_id)
        return statistics
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"获取学生统计信息失败: {str(e)}")
        raise HTTPException(status_code=500, detail="获取学生统计信息失败")

# 教师数据看板API端点
@app.get("/api/teacher/dashboard")
def get_teacher_dashboard(class_id: int = None, db: Session = Depends(get_db)):
    """获取教师数据看板综合数据"""
    try:
        # 输入验证
        if class_id is not None and class_id <= 0:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="班级ID必须为正整数"
            )
        
        logger.info(f"获取教师数据看板: class_id={class_id}")
        
        dashboard_data = crud.get_teacher_dashboard_data(db, class_id)
        return dashboard_data
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"获取教师数据看板失败: {str(e)}")
        raise HTTPException(status_code=500, detail="获取教师数据看板失败")

@app.get("/api/teacher/dashboard/overview")
def get_dashboard_overview(class_id: int = None, db: Session = Depends(get_db)):
    """获取数据看板概览信息"""
    try:
        # 输入验证
        if class_id is not None and class_id <= 0:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="班级ID必须为正整数"
            )
        
        logger.info(f"获取数据看板概览: class_id={class_id}")
        
        overview = crud.get_dashboard_overview(db, class_id)
        return overview
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"获取概览数据失败: {str(e)}")
        raise HTTPException(status_code=500, detail="获取概览数据失败")

@app.get("/api/teacher/dashboard/students")
def get_dashboard_students(class_id: int = None, db: Session = Depends(get_db)):
    """获取学生个体分析数据"""
    try:
        # 输入验证
        if class_id is not None and class_id <= 0:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="班级ID必须为正整数"
            )
        
        logger.info(f"获取学生个体分析数据: class_id={class_id}")
        
        students_data = crud.get_dashboard_students_data(db, class_id)
        return students_data
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"获取学生数据失败: {str(e)}")
        raise HTTPException(status_code=500, detail="获取学生数据失败")

@app.get("/api/teacher/dashboard/errors")
def get_dashboard_errors(class_id: int = None, db: Session = Depends(get_db)):
    """获取共性错题分析数据"""
    try:
        # 输入验证
        if class_id is not None and class_id <= 0:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="班级ID必须为正整数"
            )
        
        logger.info(f"获取共性错题分析数据: class_id={class_id}")
        
        errors_data = crud.get_dashboard_errors_data(db, class_id)
        return errors_data
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"获取错题数据失败: {str(e)}")
        raise HTTPException(status_code=500, detail="获取错题数据失败")

@app.get("/api/teacher/dashboard/suggestions")
def get_dashboard_suggestions(class_id: int = None, db: Session = Depends(get_db)):
    """获取智能教学建议数据"""
    try:
        # 输入验证
        if class_id is not None and class_id <= 0:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="班级ID必须为正整数"
            )
        
        logger.info(f"获取智能教学建议数据: class_id={class_id}")
        
        suggestions_data = crud.get_dashboard_suggestions_data(db, class_id)
        return suggestions_data
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"获取教学建议失败: {str(e)}")
        raise HTTPException(status_code=500, detail="获取教学建议失败")

@app.post("/api/teacher/dashboard/export")
def export_dashboard_data(export_request: schemas.ExportRequest, db: Session = Depends(get_db)):
    """导出数据看板数据"""
    try:
        # 输入验证
        if not export_request:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="导出请求不能为空"
            )
        
        if hasattr(export_request, 'class_id') and export_request.class_id is not None and export_request.class_id <= 0:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="班级ID必须为正整数"
            )
        
        logger.info(f"导出数据看板数据: {export_request}")
        
        export_data = crud.export_dashboard_data(db, export_request)
        return export_data
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"导出数据失败: {str(e)}")
        raise HTTPException(status_code=500, detail="导出数据失败")

# 管理员后台接口
@app.get("/admin", response_class=HTMLResponse)
def admin_page(request: Request, username: str = Depends(get_admin_user)):
    """管理员后台页面"""
    try:
        # 读取admin.html文件
        admin_html_path = os.path.join(os.path.dirname(__file__), "admin.html")
        with open(admin_html_path, "r", encoding="utf-8") as f:
            html_content = f.read()
        return HTMLResponse(content=html_content)
    except FileNotFoundError:
        # 如果文件不存在，返回错误信息
        return HTMLResponse(
            content="<h1>错误：admin.html文件未找到</h1><p>请确保admin.html文件存在于backend目录中。</p>",
            status_code=404
        )

@app.post("/admin/questions")
async def update_questions(
    request: Request,
    db: Session = Depends(get_db),
    username: str = Depends(authenticate_admin)
):
    """批量更新题目 - 支持JSON格式或自然语言prompt"""
    try:
        # 获取客户端IP
        client_ip = get_client_ip(request)
        
        # 速率限制检查
        if not check_rate_limit(client_ip):
            logger.warning(f"管理员批量更新题目速率限制触发: IP={client_ip}, username={username}")
            raise HTTPException(
                status_code=status.HTTP_429_TOO_MANY_REQUESTS,
                detail="操作过于频繁，请稍后再试"
            )
        
        logger.info(f"管理员批量更新题目: username={username}, IP={client_ip}")
        
        # 获取请求体
        body = await request.body()
        if len(body) > 1024 * 1024:  # 限制1MB
            raise HTTPException(
                status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
                detail="请求体过大"
            )
        
        raw_data = body.decode('utf-8')
        
        # 尝试解析为JSON
        try:
            import json
            data = json.loads(raw_data)
            
            # 如果是数组，直接处理为题目数据
            if isinstance(data, list):
                # 输入验证
                if len(data) > 100:  # 限制批量题目数量
                    raise HTTPException(
                        status_code=status.HTTP_400_BAD_REQUEST,
                        detail="单次最多只能更新100道题目"
                    )
                
                questions = [schemas.QuestionCreate(**q) for q in data]
                crud.batch_create_questions(db, questions)
                
                # 清除相关缓存
                CacheManager.clear_questions_cache()
                
                logger.info(f"管理员成功批量更新题目: {len(questions)}道题目")
                return {"message": "题目更新成功"}
            
            # 如果是对象且包含questions字段
            elif isinstance(data, dict) and "questions" in data:
                # 输入验证
                if len(data["questions"]) > 100:
                    raise HTTPException(
                        status_code=status.HTTP_400_BAD_REQUEST,
                        detail="单次最多只能更新100道题目"
                    )
                
                questions = [schemas.QuestionCreate(**q) for q in data["questions"]]
                crud.batch_create_questions(db, questions)
                
                # 清除相关缓存
                CacheManager.clear_questions_cache()
                
                logger.info(f"管理员成功批量更新题目: {len(questions)}道题目")
                return {"message": "题目更新成功"}
            
            # 如果是对象且包含prompt字段，使用AI生成
            elif isinstance(data, dict) and "prompt" in data:
                prompt = data["prompt"]
                generated_questions = generate_questions_from_prompt(prompt, db)
                # 转换数据格式：snake_case -> camelCase
                converted_questions = []
                for q in generated_questions:
                    converted_q = {
                        "questionContent": q["question_content"],
                        "optionA": q["option_a"],
                        "optionB": q["option_b"],
                        "optionC": q["option_c"],
                        "optionD": q["option_d"],
                        "answer": q["answer"],
                        "knowledgePoint": q["knowledge_point"]
                    }
                    converted_questions.append(converted_q)
                
                questions = [schemas.QuestionCreate(**q) for q in converted_questions]
                crud.batch_create_questions(db, questions)
                return {
                    "message": "基于AI生成的题目更新成功",
                    "generated_count": len(questions),
                    "generated_questions": converted_questions
                }
            
            else:
                raise ValueError("JSON格式不正确，应为题目数组或包含prompt字段的对象")
                
        except json.JSONDecodeError:
            # 如果不是JSON格式，当作自然语言prompt处理
            prompt = raw_data.strip()
            if not prompt:
                raise ValueError("输入内容不能为空")
            
            # 使用AI生成题目（使用数据库配置）
            generated_questions = generate_questions_from_prompt(prompt, db)
            # 转换数据格式：snake_case -> camelCase
            converted_questions = []
            for q in generated_questions:
                converted_q = {
                    "questionContent": q["question_content"],
                    "optionA": q["option_a"],
                    "optionB": q["option_b"],
                    "optionC": q["option_c"],
                    "optionD": q["option_d"],
                    "answer": q["answer"],
                    "knowledgePoint": q["knowledge_point"]
                }
                converted_questions.append(converted_q)
            
            questions = [schemas.QuestionCreate(**q) for q in converted_questions]
            crud.batch_create_questions(db, questions)
            
            return {
                "message": "基于AI生成的题目更新成功",
                "generated_count": len(questions),
                "generated_questions": converted_questions
            }
            
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"更新失败: {str(e)}")

@app.get("/admin/questions", response_model=List[schemas.Question])
def get_admin_questions(
    request: Request,
    db: Session = Depends(get_db),
    username: str = Depends(authenticate_admin)
):
    """获取所有题目（管理员）"""
    try:
        client_ip = get_client_ip(request)
        logger.info(f"管理员获取题目列表: username={username}, IP={client_ip}")
        
        questions = crud.get_questions(db)
        logger.info(f"成功返回 {len(questions)} 道题目")
        return questions
        
    except Exception as e:
        logger.error(f"管理员获取题目列表失败: {str(e)}")
        raise HTTPException(status_code=500, detail="获取题目列表失败")

@app.get("/admin/questions/{question_id}", response_model=schemas.Question)
def get_admin_question(
    question_id: int,
    request: Request,
    db: Session = Depends(get_db),
    username: str = Depends(authenticate_admin)
):
    """获取单个题目（管理员）"""
    try:
        # 输入验证
        if question_id <= 0:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="题目ID必须为正整数"
            )
        
        client_ip = get_client_ip(request)
        logger.info(f"管理员获取单个题目: question_id={question_id}, username={username}, IP={client_ip}")
        
        question = crud.get_question(db, question_id)
        if not question:
            raise HTTPException(status_code=404, detail="题目不存在")
        
        return question
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"管理员获取单个题目失败: {str(e)}")
        raise HTTPException(status_code=500, detail="获取题目失败")

@app.put("/admin/questions/{question_id}", response_model=schemas.Question)
def update_question(
    question_id: int,
    question: schemas.QuestionCreate,
    request: Request,
    db: Session = Depends(get_db),
    username: str = Depends(authenticate_admin)
):
    """更新单个题目"""
    try:
        # 输入验证
        if question_id <= 0:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="题目ID必须为正整数"
            )
        
        client_ip = get_client_ip(request)
        logger.info(f"管理员更新题目: question_id={question_id}, username={username}, IP={client_ip}")
        
        db_question = crud.update_question(db, question_id, question)
        if not db_question:
            raise HTTPException(status_code=404, detail="题目不存在")
        
        # 清除相关缓存
        CacheManager.clear_questions_cache()
        
        logger.info(f"管理员成功更新题目: question_id={question_id}")
        return db_question
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"管理员更新题目失败: {str(e)}")
        raise HTTPException(status_code=500, detail="更新题目失败")

@app.delete("/admin/questions/{question_id}")
def delete_question(
    question_id: int,
    request: Request,
    db: Session = Depends(get_db),
    username: str = Depends(authenticate_admin)
):
    """删除题目"""
    try:
        # 输入验证
        if question_id <= 0:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="题目ID必须为正整数"
            )
        
        client_ip = get_client_ip(request)
        logger.info(f"管理员删除题目: question_id={question_id}, username={username}, IP={client_ip}")
        
        success = crud.delete_question(db, question_id)
        if not success:
            raise HTTPException(status_code=404, detail="题目不存在")
        
        # 清除相关缓存
        CacheManager.clear_questions_cache()
        
        logger.info(f"管理员成功删除题目: question_id={question_id}")
        return {"message": "题目删除成功"}
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"管理员删除题目失败: {str(e)}")
        raise HTTPException(status_code=500, detail="删除题目失败")

@app.post("/admin/questions/create", response_model=schemas.Question)
def create_question(
    question: schemas.QuestionCreate,
    request: Request,
    db: Session = Depends(get_db),
    username: str = Depends(authenticate_admin)
):
    """创建新题目"""
    try:
        client_ip = get_client_ip(request)
        logger.info(f"管理员创建题目: username={username}, IP={client_ip}")

        new_question = crud.create_question(db, question)

        # 清除相关缓存
        CacheManager.clear_questions_cache()

        logger.info(f"管理员成功创建题目: question_id={new_question.id}")
        return new_question

    except Exception as e:
        logger.error(f"管理员创建题目失败: {str(e)}")
        raise HTTPException(status_code=500, detail="创建题目失败")

@app.post("/admin/questions/{question_id}/move")
def move_single_question(
    question_id: int,
    move_data: dict,
    request: Request,
    db: Session = Depends(get_db),
    username: str = Depends(authenticate_admin)
):
    """移动单个题目到指定题库"""
    try:
        if question_id <= 0:
            raise HTTPException(status_code=400, detail="题目ID必须为正整数")

        question_bank_id = move_data.get('question_bank_id')

        client_ip = get_client_ip(request)
        logger.info(f"管理员移动题目: question_id={question_id}, target_bank={question_bank_id}, username={username}, IP={client_ip}")

        # 验证题目是否存在
        question = crud.get_question(db, question_id)
        if not question:
            raise HTTPException(status_code=404, detail="题目不存在")

        # 如果目标题库不为空，验证题库是否存在
        if question_bank_id is not None:
            bank = crud.get_question_bank(db, question_bank_id)
            if not bank:
                raise HTTPException(status_code=404, detail="目标题库不存在")

        # 移动题目
        success = crud.move_single_question(db, question_id, question_bank_id)
        if not success:
            raise HTTPException(status_code=500, detail="移动题目失败")

        # 清除相关缓存
        CacheManager.clear_questions_cache()

        target_info = f"题库 {question_bank_id}" if question_bank_id else "无题库"
        logger.info(f"管理员成功移动题目 {question_id} 到 {target_info}")
        return {"message": "题目移动成功"}

    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"移动题目失败: {str(e)}")
        raise HTTPException(status_code=500, detail="移动题目失败")

@app.post("/admin/questions/{question_id}/copy")
def copy_single_question(
    question_id: int,
    copy_data: dict,
    request: Request,
    db: Session = Depends(get_db),
    username: str = Depends(authenticate_admin)
):
    """复制单个题目到指定题库"""
    try:
        if question_id <= 0:
            raise HTTPException(status_code=400, detail="题目ID必须为正整数")

        question_bank_id = copy_data.get('question_bank_id')
        if question_bank_id is None:
            raise HTTPException(status_code=400, detail="目标题库ID不能为空")

        client_ip = get_client_ip(request)
        logger.info(f"管理员复制题目: question_id={question_id}, target_bank={question_bank_id}, username={username}, IP={client_ip}")

        # 验证题目是否存在
        question = crud.get_question(db, question_id)
        if not question:
            raise HTTPException(status_code=404, detail="题目不存在")

        # 验证目标题库是否存在
        bank = crud.get_question_bank(db, question_bank_id)
        if not bank:
            raise HTTPException(status_code=404, detail="目标题库不存在")

        # 复制题目
        new_question = crud.copy_question_to_bank(db, question_id, question_bank_id)
        if not new_question:
            raise HTTPException(status_code=500, detail="复制题目失败")

        # 清除相关缓存
        CacheManager.clear_questions_cache()

        logger.info(f"管理员成功复制题目 {question_id} 到题库 {question_bank_id}，新题目ID: {new_question.id}")
        return {"message": "题目复制成功", "new_question_id": new_question.id}

    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"复制题目失败: {str(e)}")
        raise HTTPException(status_code=500, detail="复制题目失败")

@app.post("/admin/ai-generate")
async def ai_generate_questions(
    request: schemas.AIGenerateRequest,
    db: Session = Depends(get_db)
):
    """AI智能生成题目 - 支持单次生成和批量生成"""
    try:
        # 输入验证
        if request.batch_mode:
            if not request.prompts:
                raise HTTPException(status_code=400, detail="批量模式下必须提供prompts列表")
        else:
            if not request.knowledge_point:
                raise HTTPException(status_code=400, detail="单次模式下必须提供knowledge_point")

        if request.difficulty and request.difficulty not in ["easy", "medium", "hard"]:
            raise HTTPException(status_code=400, detail="难度等级必须是easy、medium或hard")
        
        # 使用LLM API生成题目
        api_instance = LLMAPI(db)
        
        if request.batch_mode:
            # 批量生成模式
            questions_data = api_instance.generate_questions_batch(
                request.prompts, 
                request.count
            )
        else:
            # 单次生成模式
            questions_data = api_instance.generate_questions(
                request.knowledge_point,
                request.count,
                request.difficulty,
                request.topic
            )
        
        # 转换数据格式：snake_case -> camelCase
        converted_questions = []
        for q in questions_data:
            converted_q = {
                "questionContent": q["question_content"],
                "optionA": q["option_a"],
                "optionB": q["option_b"],
                "optionC": q["option_c"],
                "optionD": q["option_d"],
                "answer": q["answer"],
                "knowledgePoint": q["knowledge_point"],
                "explanation": q.get("explanation", "详细解析待补充"),
                "wrongAnalysis": q.get("wrong_analysis", "错误选项分析待补充")
            }
            converted_questions.append(converted_q)
        
        # 准备知识点信息用于题库描述
        knowledge_points_info = ""
        generation_mode = "batch" if request.batch_mode else "single"

        if request.batch_mode and request.prompts:
            # 批量模式：显示所有知识点
            knowledge_points_info = "、".join(request.prompts[:5])  # 最多显示前5个
            if len(request.prompts) > 5:
                knowledge_points_info += f" 等{len(request.prompts)}个知识点"
        else:
            # 单次模式：显示主要知识点
            parts = []
            if request.knowledge_point:
                parts.append(request.knowledge_point)
            if request.topic:
                parts.append(f"主题：{request.topic}")
            if request.difficulty:
                difficulty_map = {"easy": "简单", "medium": "中等", "hard": "困难"}
                parts.append(f"难度：{difficulty_map.get(request.difficulty, request.difficulty)}")
            knowledge_points_info = "，".join(parts)

        # 使用新的AI生成题目方式（创建新题库，不覆盖现有题目）
        questions = [schemas.QuestionCreate(**q) for q in converted_questions]
        db_questions, new_bank = crud.ai_generate_questions_to_new_bank(
            db, questions,
            knowledge_points=knowledge_points_info,
            generation_mode=generation_mode
        )
        
        # 构建响应消息
        mode_text = "批量生成" if request.batch_mode else "智能生成"
        difficulty_text = f"（难度：{request.difficulty}）" if request.difficulty else ""
        topic_text = f"（主题：{request.topic}）" if request.topic else ""
        
        return {
            "message": f"成功{mode_text} {len(questions)} 道题目{difficulty_text}{topic_text}，已创建新题库：{new_bank.name}",
            "questions": converted_questions,
            "generation_info": {
                "mode": "batch" if request.batch_mode else "single",
                "count": len(questions),
                "difficulty": request.difficulty,
                "topic": request.topic,
                "prompts_used": len(request.prompts) if request.batch_mode else 1,
                "new_bank": {
                    "id": new_bank.id,
                    "name": new_bank.name,
                    "description": new_bank.description,
                    "is_active": new_bank.is_active
                }
            }
        }
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"AI生成题目失败: {str(e)}")
        raise HTTPException(status_code=500, detail=f"生成题目失败: {str(e)}")

@app.delete("/admin/submissions/{submission_id}")
def delete_submission(
    submission_id: int,
    db: Session = Depends(get_db),
    username: str = Depends(authenticate_admin)
):
    """删除提交记录"""
    success = crud.delete_submission(db, submission_id)
    if not success:
        raise HTTPException(status_code=404, detail="提交记录不存在")
    return {"message": "提交记录删除成功"}

# 大语言模型配置管理API
@app.get("/admin/llm-configs")
def get_llm_configs(
    db: Session = Depends(get_db),
    username: str = Depends(authenticate_admin)
):
    """获取所有大语言模型配置"""
    configs = crud.get_llm_configs(db)
    return configs

@app.get("/admin/llm-configs/{config_id}")
def get_llm_config(
    config_id: int,
    db: Session = Depends(get_db),
    username: str = Depends(authenticate_admin)
):
    """根据ID获取大语言模型配置"""
    config = crud.get_llm_config(db, config_id)
    if not config:
        raise HTTPException(status_code=404, detail="配置不存在")
    return config

@app.post("/admin/llm-configs")
def create_llm_config(
    config: schemas.LLMConfigCreate,
    db: Session = Depends(get_db),
    username: str = Depends(authenticate_admin)
):
    """创建新的大语言模型配置"""
    try:
        new_config = crud.create_llm_config(db, config)
        return new_config
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))

@app.put("/admin/llm-configs/{config_id}")
def update_llm_config(
    config_id: int,
    config: schemas.LLMConfigUpdate,
    db: Session = Depends(get_db),
    username: str = Depends(authenticate_admin)
):
    """更新大语言模型配置"""
    try:
        updated_config = crud.update_llm_config(db, config_id, config)
        if not updated_config:
            raise HTTPException(status_code=404, detail="配置不存在")
        return updated_config
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))

@app.delete("/admin/llm-configs/{config_id}")
def delete_llm_config(
    config_id: int,
    db: Session = Depends(get_db),
    username: str = Depends(authenticate_admin)
):
    """删除大语言模型配置"""
    try:
        success = crud.delete_llm_config(db, config_id)
        if not success:
            raise HTTPException(status_code=404, detail="配置不存在")
        return {"message": "配置删除成功"}
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))

@app.post("/admin/llm-configs/{config_id}/activate")
def activate_llm_config(
    config_id: int,
    db: Session = Depends(get_db),
    username: str = Depends(authenticate_admin)
):
    """激活大语言模型配置"""
    try:
        success = crud.activate_llm_config(db, config_id)
        if not success:
            raise HTTPException(status_code=404, detail="配置不存在")
        return {"message": "配置激活成功"}
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))

@app.get("/admin/export/pdf/{class_id}")
async def export_pdf(class_id: int, db: Session = Depends(get_db)):
    """导出班级数据为PDF"""
    try:
        from export_utils import ExportManager
        from fastapi.responses import Response

        export_manager = ExportManager(db)
        result = export_manager.export_student_records(class_id=class_id, format_type="pdf")

        if result["status"] == "success":
            return Response(
                content=result["content"],
                media_type=result["content_type"],
                headers={
                    "Content-Disposition": f"attachment; filename={result['filename']}"
                }
            )
        else:
            raise HTTPException(status_code=500, detail="PDF导出失败")

    except Exception as e:
        logger.error(f"PDF导出失败: {e}")
        raise HTTPException(status_code=500, detail=f"导出失败: {str(e)}")

# 管理员学生记录导出接口
@app.get("/admin/export/student-records")
async def export_student_records(
    student_name: str = None,
    class_id: int = None,
    start_date: str = None,
    end_date: str = None,
    format_type: str = "excel",
    db: Session = Depends(get_db)
):
    """导出学生记录"""
    try:
        from export_utils import ExportManager
        from fastapi.responses import Response

        export_manager = ExportManager(db)
        result = export_manager.export_student_records(
            student_name=student_name,
            class_id=class_id,
            start_date=start_date,
            end_date=end_date,
            format_type=format_type
        )

        if result["status"] == "success":
            return Response(
                content=result["content"],
                media_type=result["content_type"],
                headers={
                    "Content-Disposition": f"attachment; filename={result['filename']}"
                }
            )
        else:
            raise HTTPException(status_code=500, detail="导出失败")

    except Exception as e:
        logger.error(f"导出学生记录失败: {e}")
        raise HTTPException(status_code=500, detail=f"导出失败: {str(e)}")

# 班级配置相关API端点
@app.get("/admin/class-config", response_model=schemas.ClassConfig)
async def get_class_config(db: Session = Depends(get_db)):
    """获取当前班级配置"""
    try:
        config = crud.get_class_config(db)
        return config
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取班级配置失败: {str(e)}")

@app.put("/admin/class-config")
async def update_class_config(
    current_class_id: int = Form(...),
    class_name: str = Form(None),
    db: Session = Depends(get_db)
):
    """更新班级配置"""
    try:
        config = crud.update_class_config(db, current_class_id, class_name)
        return {"message": "班级配置更新成功", "config": config}
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"更新班级配置失败: {str(e)}")

@app.delete("/admin/class-submissions/{class_id}")
async def delete_class_submissions(
    class_id: int,
    db: Session = Depends(get_db),
    username: str = Depends(authenticate_admin)
):
    """删除指定班级的所有学生记录"""
    try:
        success = crud.delete_class_submissions(db, class_id)
        if success:
            return {"message": f"班级 {class_id} 的所有学生记录已删除"}
        else:
            raise HTTPException(status_code=500, detail="删除失败")
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"删除班级记录失败: {str(e)}")

@app.delete("/admin/questions/clear-all")
async def clear_all_questions(
    db: Session = Depends(get_db),
    username: str = Depends(authenticate_admin)
):
    """清空所有题目"""
    try:
        crud.clear_all_questions(db)
        return {"message": "所有题目已清空"}
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"清空题目失败: {str(e)}")

# 题库管理API接口
@app.get("/admin/question-banks", response_model=List[schemas.QuestionBank])
def get_question_banks(
    skip: int = 0,
    limit: int = 100,
    db: Session = Depends(get_db),
    username: str = Depends(authenticate_admin)
):
    """获取题库列表"""
    try:
        banks = crud.get_question_banks(db, skip=skip, limit=limit)
        return banks
    except Exception as e:
        logger.error(f"获取题库列表失败: {str(e)}")
        raise HTTPException(status_code=500, detail="获取题库列表失败")

@app.get("/admin/question-banks/active")
def get_active_question_bank(
    db: Session = Depends(get_db),
    username: str = Depends(authenticate_admin)
):
    """获取当前激活的题库"""
    try:
        active_bank = crud.get_active_question_bank(db)
        if not active_bank:
            return {"bank": None, "message": "当前没有激活的题库"}
        
        return {
            "bank": {
                "id": active_bank.id,
                "name": active_bank.name,
                "description": active_bank.description,
                "question_count": active_bank.question_count,
                "created_time": active_bank.created_time,
                "updated_time": active_bank.updated_time
            }
        }
    except Exception as e:
        logger.error(f"获取激活题库失败: {str(e)}")
        raise HTTPException(status_code=500, detail="获取激活题库失败")

@app.get("/admin/question-banks/{bank_id}", response_model=schemas.QuestionBankDetail)
def get_question_bank(
    bank_id: int,
    db: Session = Depends(get_db),
    username: str = Depends(authenticate_admin)
):
    """获取单个题库详情"""
    try:
        if bank_id <= 0:
            raise HTTPException(status_code=400, detail="题库ID必须为正整数")
        
        result = crud.get_question_bank_with_questions(db, bank_id)
        if not result:
            raise HTTPException(status_code=404, detail="题库不存在")
        
        return schemas.QuestionBankDetail(
            id=result["bank"].id,
            name=result["bank"].name,
            description=result["bank"].description,
            created_by=result["bank"].created_by,
            created_time=result["bank"].created_time,
            updated_time=result["bank"].updated_time,
            question_count=result["bank"].question_count,
            questions=result["questions"]
        )
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"获取题库详情失败: {str(e)}")
        raise HTTPException(status_code=500, detail="获取题库详情失败")

@app.post("/admin/question-banks", response_model=schemas.QuestionBank)
def create_question_bank(
    bank: schemas.QuestionBankCreate,
    db: Session = Depends(get_db),
    username: str = Depends(authenticate_admin)
):
    """创建题库"""
    try:
        # 输入验证
        if not bank.name or not bank.name.strip():
            raise HTTPException(status_code=400, detail="题库名称不能为空")
        
        # 设置创建者为当前管理员
        bank.created_by = username
        
        new_bank = crud.create_question_bank(db, bank)
        logger.info(f"管理员 {username} 创建题库: {new_bank.name}")
        return new_bank
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"创建题库失败: {str(e)}")
        raise HTTPException(status_code=500, detail="创建题库失败")

@app.put("/admin/question-banks/{bank_id}", response_model=schemas.QuestionBank)
def update_question_bank(
    bank_id: int,
    bank: schemas.QuestionBankUpdate,
    db: Session = Depends(get_db),
    username: str = Depends(authenticate_admin)
):
    """更新题库"""
    try:
        if bank_id <= 0:
            raise HTTPException(status_code=400, detail="题库ID必须为正整数")
        
        updated_bank = crud.update_question_bank(db, bank_id, bank)
        if not updated_bank:
            raise HTTPException(status_code=404, detail="题库不存在")
        
        logger.info(f"管理员 {username} 更新题库: {updated_bank.name}")
        return updated_bank
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"更新题库失败: {str(e)}")
        raise HTTPException(status_code=500, detail="更新题库失败")

@app.delete("/admin/question-banks/{bank_id}")
def delete_question_bank(
    bank_id: int,
    db: Session = Depends(get_db),
    username: str = Depends(authenticate_admin)
):
    """删除题库"""
    try:
        if bank_id <= 0:
            raise HTTPException(status_code=400, detail="题库ID必须为正整数")
        
        # 获取题库信息用于日志
        bank = crud.get_question_bank(db, bank_id)
        if not bank:
            raise HTTPException(status_code=404, detail="题库不存在")
        
        success = crud.delete_question_bank(db, bank_id)
        if not success:
            raise HTTPException(status_code=404, detail="题库不存在")
        
        # 清除相关缓存
        CacheManager.clear_questions_cache()
        
        logger.info(f"管理员 {username} 删除题库: {bank.name}")
        return {"message": "题库删除成功"}
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"删除题库失败: {str(e)}")
        raise HTTPException(status_code=500, detail="删除题库失败")

@app.get("/admin/question-banks/{bank_id}/questions", response_model=List[schemas.Question])
def get_question_bank_questions(
    bank_id: int,
    db: Session = Depends(get_db),
    username: str = Depends(authenticate_admin)
):
    """获取指定题库的题目列表"""
    try:
        if bank_id <= 0:
            raise HTTPException(status_code=400, detail="题库ID必须为正整数")

        # 验证题库是否存在
        bank = crud.get_question_bank(db, bank_id)
        if not bank:
            raise HTTPException(status_code=404, detail="题库不存在")

        # 获取题库中的题目
        questions = crud.get_questions_by_bank(db, bank_id)
        logger.info(f"管理员 {username} 获取题库 {bank.name} 的题目列表，共 {len(questions)} 道题目")
        return questions

    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"获取题库题目失败: {str(e)}")
        raise HTTPException(status_code=500, detail="获取题库题目失败")

@app.post("/admin/question-banks/{bank_id}/move-questions")
def move_questions_to_bank(
    bank_id: int,
    question_ids: List[int],
    db: Session = Depends(get_db),
    username: str = Depends(authenticate_admin)
):
    """将题目移动到指定题库"""
    try:
        if bank_id <= 0:
            raise HTTPException(status_code=400, detail="题库ID必须为正整数")

        if not question_ids:
            raise HTTPException(status_code=400, detail="题目ID列表不能为空")

        success = crud.move_questions_to_bank(db, question_ids, bank_id)
        if not success:
            raise HTTPException(status_code=404, detail="题库不存在")

        # 清除相关缓存
        CacheManager.clear_questions_cache()

        logger.info(f"管理员 {username} 将 {len(question_ids)} 道题目移动到题库 {bank_id}")
        return {"message": f"成功移动 {len(question_ids)} 道题目"}
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"移动题目失败: {str(e)}")
        raise HTTPException(status_code=500, detail="移动题目失败")

@app.post("/admin/question-banks/{bank_id}/copy-questions")
def copy_questions_to_bank(
    bank_id: int,
    question_ids: List[int],
    db: Session = Depends(get_db),
    username: str = Depends(authenticate_admin)
):
    """将题目复制到指定题库"""
    try:
        if bank_id <= 0:
            raise HTTPException(status_code=400, detail="题库ID必须为正整数")

        if not question_ids:
            raise HTTPException(status_code=400, detail="题目ID列表不能为空")

        # 验证目标题库是否存在
        bank = crud.get_question_bank(db, bank_id)
        if not bank:
            raise HTTPException(status_code=404, detail="目标题库不存在")

        # 批量复制题目
        success_count, failed_count = crud.copy_questions_to_bank(db, question_ids, bank_id)

        # 清除相关缓存
        CacheManager.clear_questions_cache()

        logger.info(f"管理员 {username} 将 {success_count} 道题目复制到题库 {bank_id}，失败 {failed_count} 道")

        if failed_count == 0:
            return {"message": f"成功复制 {success_count} 道题目"}
        else:
            return {"message": f"复制完成：成功 {success_count} 道，失败 {failed_count} 道"}
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"复制题目失败: {str(e)}")
        raise HTTPException(status_code=500, detail="复制题目失败")

@app.post("/admin/question-banks/{bank_id}/activate")
def activate_question_bank(
    bank_id: int,
    db: Session = Depends(get_db),
    username: str = Depends(authenticate_admin)
):
    """激活指定题库"""
    try:
        if bank_id <= 0:
            raise HTTPException(status_code=400, detail="题库ID必须为正整数")
        
        # 获取题库信息用于日志
        bank = crud.get_question_bank(db, bank_id)
        if not bank:
            raise HTTPException(status_code=404, detail="题库不存在")
        
        success = crud.activate_question_bank(db, bank_id)
        if not success:
            raise HTTPException(status_code=500, detail="激活题库失败")
        
        logger.info(f"管理员 {username} 激活题库: {bank.name}")
        return {"message": f"题库 '{bank.name}' 已激活"}
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"激活题库失败: {str(e)}")
        raise HTTPException(status_code=500, detail="激活题库失败")

# 前端路由（SPA支持）
# 挂载前端静态文件
if os.path.exists("frontend/dist"):
    # 先挂载静态资源
    app.mount("/assets", StaticFiles(directory="frontend/dist/assets"), name="assets")
    
    # 添加catch-all路由处理SPA路由
    @app.get("/{full_path:path}")
    async def serve_spa(full_path: str):
        """处理SPA路由，所有未匹配的路径都返回index.html"""
        import os
        index_path = os.path.join("frontend/dist", "index.html")
        if os.path.exists(index_path):
            with open(index_path, "r", encoding="utf-8") as f:
                content = f.read()
            return HTMLResponse(content=content)
        else:
            raise HTTPException(status_code=404, detail="Frontend not found")
else:
    @app.get("/", response_class=HTMLResponse)
    def serve_frontend():
        """服务前端应用"""
        return HTMLResponse(content="<h1>高中信息技术选择题练习平台</h1><p>后端服务正常运行在端口8000</p><p>访问 <a href='http://localhost:3000'>http://localhost:3000</a> 查看前端应用</p><p>管理员界面: <a href='http://localhost:8000/admin'>http://localhost:8000/admin</a></p>", status_code=200)

# 健康检查接口
@app.get("/health")
async def health_check(db: Session = Depends(get_db)):
    """健康检查接口"""
    try:
        # 测试数据库连接
        db.execute("SELECT 1")
        database_status = "connected"
    except Exception:
        database_status = "disconnected"

    return {
        "status": "healthy",
        "database": database_status,
        "timestamp": datetime.now().isoformat(),
        "version": "1.0.0"
    }

# 学生记录查询接口
@app.get("/api/student-records")
async def get_student_records(
    student_name: str = None,
    class_id: int = None,
    start_date: str = None,
    end_date: str = None,
    db: Session = Depends(get_db)
):
    """获取学生做题记录"""
    try:
        query = db.query(database.Submission)

        if student_name:
            query = query.filter(database.Submission.student_name == student_name)
        if class_id:
            query = query.filter(database.Submission.class_id == class_id)
        if start_date:
            query = query.filter(database.Submission.submission_date >= start_date)
        if end_date:
            query = query.filter(database.Submission.submission_date <= end_date)

        submissions = query.order_by(database.Submission.submission_time.desc()).all()

        records = []
        for submission in submissions:
            records.append({
                "submission_id": submission.id,
                "student_name": submission.student_name,
                "score": submission.score,
                "submission_time": submission.submission_time.strftime("%Y-%m-%d %H:%M:%S"),
                "class_id": submission.class_id,
                "total_questions": submission.total_questions,
                "correct_answers": submission.correct_answers,
                "completion_time": submission.completion_time
            })

        return {
            "total_count": len(records),
            "records": records
        }

    except Exception as e:
        logger.error(f"获取学生记录失败: {e}")
        raise HTTPException(status_code=500, detail="获取学生记录失败")

# 学生统计信息接口
@app.get("/api/student-statistics/{student_name}")
async def get_student_statistics(
    student_name: str,
    class_id: int = None,
    db: Session = Depends(get_db)
):
    """获取学生统计信息"""
    try:
        query = db.query(database.Submission).filter(
            database.Submission.student_name == student_name
        )

        if class_id:
            query = query.filter(database.Submission.class_id == class_id)

        submissions = query.all()

        if not submissions:
            raise HTTPException(status_code=404, detail="未找到该学生的记录")

        # 计算统计信息
        scores = [s.score for s in submissions]
        total_submissions = len(submissions)
        average_score = sum(scores) / total_submissions
        best_score = max(scores)
        latest_score = submissions[-1].score if submissions else 0

        # 计算趋势
        if len(scores) >= 2:
            recent_avg = sum(scores[-3:]) / min(3, len(scores))
            early_avg = sum(scores[:3]) / min(3, len(scores))
            improvement_trend = "上升" if recent_avg > early_avg else "下降" if recent_avg < early_avg else "稳定"
        else:
            improvement_trend = "数据不足"

        # 获取知识点掌握情况
        weak_points = []
        strong_points = []

        # 这里可以添加更复杂的知识点分析逻辑

        return {
            "student_name": student_name,
            "total_submissions": total_submissions,
            "average_score": round(average_score, 1),
            "best_score": best_score,
            "latest_score": latest_score,
            "improvement_trend": improvement_trend,
            "weak_knowledge_points": weak_points,
            "strong_knowledge_points": strong_points
        }

    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"获取学生统计失败: {e}")
        raise HTTPException(status_code=500, detail="获取学生统计失败")

# 教师数据导出接口
@app.post("/api/teacher/dashboard/export")
async def export_teacher_data(
    export_request: dict,
    db: Session = Depends(get_db)
):
    """导出教师数据"""
    try:
        from export_utils import ExportManager
        from fastapi.responses import Response

        class_id = export_request.get("class_id")
        format_type = export_request.get("format", "excel")

        if not class_id:
            raise HTTPException(status_code=400, detail="缺少班级ID")

        # 使用导出管理器
        export_manager = ExportManager(db)
        result = export_manager.export_teacher_dashboard_data(class_id, format_type)

        if result["status"] == "success":
            # 返回文件内容
            return Response(
                content=result["content"],
                media_type=result["content_type"],
                headers={
                    "Content-Disposition": f"attachment; filename={result['filename']}"
                }
            )
        else:
            raise HTTPException(status_code=500, detail="导出失败")

    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"导出数据失败: {e}")
        raise HTTPException(status_code=500, detail="导出数据失败")

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)